package com.mercycrazy.recruit.filter;

import com.alibaba.fastjson.JSON;
import com.mercycrazy.recruit.config.AuthExcludeProperties;
import com.mercycrazy.recruit.enums.StatusCode;
import com.mercycrazy.recruit.util.SwaggerUtil;
import com.mercycrazy.recruit.util.jwt.JWTProperties;
import com.mercycrazy.recruit.util.jwt.JWTUtil;
import com.mercycrazy.recruit.vo.ApiResponse;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import static com.mercycrazy.recruit.constant.CommonConstant.JWT_USER_INFO_KEY;
import static com.mercycrazy.recruit.constant.CommonConstant.LOGIN_PLATFORM;

/**
 * JWT 鉴权的网关全局过滤器
 *
 * @author <a href="mailto:1443424326@qq.com">mercycrazy</a>
 * @since 2023-01-16
 */
@Slf4j
@Component
@SuppressWarnings("all")
public class JWTAuthFilter implements GlobalFilter, Ordered {

    private final AuthExcludeProperties authExcludeProperties;

    private final JWTProperties jwtProperties;

    // 路径匹配规则器
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    public JWTAuthFilter(AuthExcludeProperties authExcludeProperties,
                         JWTProperties jwtProperties) {
        this.authExcludeProperties = authExcludeProperties;
        this.jwtProperties = jwtProperties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 1. 放行不需要鉴权的路径
        if (!isUrlNeedAuth(exchange)) {
            return chain.filter(exchange);
        }
        if (SwaggerUtil.checkSpringDocUrl(exchange.getRequest().getURI().getPath())) {
            return chain.filter(exchange);
        }

        // 2. 从请求头冲获取 Token
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst(JWT_USER_INFO_KEY);

        // 3. 解析处理 Token
        if (StringUtils.isNotBlank(token)) {
            try {
                // 截断 token prefix, 获取用户登录来源
                int index = token.indexOf("@");
                String platform = token.substring(0, index);

                Map<String, ?> jwtBody = JWTUtil.checkAndParseTokenWithPrefix(
                        token, jwtProperties.getSecretKey()
                );
                String userInfoStr = MapUtils.getString(jwtBody, JWT_USER_INFO_KEY);
                return chain.filter(
                        // http header 中传输中文会乱码, 需要使用 URLEncoder 和 URLDecoder 处理
                        generateNewHeader(exchange, platform,
                                URLEncoder.encode(userInfoStr, StandardCharsets.UTF_8.name())
                        )
                );
            } catch (ExpiredJwtException e) {
                log.error("token has expired: {}}]", e.getMessage(), e);
                return renderErrorMsg(exchange, StatusCode.JWT_EXPIRE_ERROR);
            } catch (Exception e) {
                log.error("parse jwt token error: [{}]", e.getMessage(), e);
                return renderErrorMsg(exchange, StatusCode.JWT_SIGNATURE_ERROR);
            }
        }

        return renderErrorMsg(exchange, StatusCode.UN_LOGIN);
    }

    /**
     * 生成新的 Request, 用 header 保存用户信息, 以便在 ServletHttpRequest 中可以取到
     */
    private ServerWebExchange generateNewHeader(ServerWebExchange exchange,
                                                String platform, String userInfo) {

        ServerHttpRequest newRequest = exchange.getRequest()
                .mutate()
                .header(JWT_USER_INFO_KEY, userInfo)
                .header(LOGIN_PLATFORM, platform)
                .build();

        return exchange.mutate().request(newRequest).build();
    }

    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE + 1;
    }

    /**
     * Gateway 基于 Webflux, 不走 Spring MVC 全局异常处理器, 所以需要对异常处理单独包装
     */
    private Mono<Void> renderErrorMsg(ServerWebExchange exchange,
                                      StatusCode statusCode) {

        ServerHttpResponse response = exchange.getResponse();

        response.setStatusCode(HttpStatus.UNAUTHORIZED);

        ApiResponse<?> errorResponse = new ApiResponse<>(statusCode);

        if (!response.getHeaders().containsKey("Content-Type")) {
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        }

        String resultJson = JSON.toJSONString(errorResponse);

        DataBuffer dataBuffer = response.bufferFactory()
                .wrap(resultJson.getBytes(StandardCharsets.UTF_8));

        return response.writeWith(Mono.just(dataBuffer));
    }

    /**
     * 判断请求是否需要鉴权
     */
    private boolean isUrlNeedAuth(ServerWebExchange exchange) {

        String url = exchange.getRequest().getURI().getPath();

        List<String> excludeUrlList = authExcludeProperties.getUrls();
        if (!CollectionUtils.isEmpty(excludeUrlList)) {
            for (String excludeUrl : excludeUrlList) {
                if (antPathMatcher.matchStart(excludeUrl, url)) {
                    return false;
                }
            }
        }

        return true;
    }
}
